#import "Alexandra.h"
#import "ArticleViewControl.h"
#import <misckit/MiscAppDefaults.h>
#import "response_codes.h"
#import "FaceView.h"
#import <misckit/MiscClockView.h>
#import "parse-header.h"
#import "rfc822realname.h"
#include <ctype.h>

#define QUOTESTRING ">"
#define ISEMPTY(string) ((string==NULL)||(*(string)=='\0'))
#define FORCEBREAK "\n\t -·1234567890>#}:"

@implementation ArticleViewControl

- init
{
   [super init];
   noArticle=TRUE;
	rot13=FALSE;
	[ERROR_MANAGER addObserver:self
	 					selector:@selector(updateText)
						forError:ENOTEPrefsChanged];
	[self updateText];
	
   return self;
}

- awakeFromNib
{
   [theText setFontPanelEnabled:FALSE];
   [clockView setMilitaryTime:[NXApp defaultBoolValue:"24HourClock"]];
	[clockView setHide:YES];
	
	return self;
}

- free
{
   if(articleHeader)
      free(articleHeader);
	if(articleBody)
		free(articleBody);
	[ERROR_MANAGER removeObserver:self forError:ENOTEPrefsChanged];

   return [super free];
}

- updateText
{
	[self displayArticleScrollUp:NO];
	
   return self;
}

- (int)loadArticle:(Article *)theArticle fromGroup:(const char *)theGroup
{
   int statusCode;
	time_t t;

	rot13=FALSE;
   // get article header + body
	if(articleHeader)
      free(articleHeader);	
   statusCode=[nntpServer loadArticleHeader:theArticle toString:&articleHeader];
   if(statusCode!=OK_HEAD){
      return statusCode;
   }

	subject=[theArticle header]->fieldBody[SUBJECT];
	from=[theArticle header]->fieldBody[FROM];
	organization=[theArticle header]->fieldBody[ORGANIZATION];

	
	if(articleBody)
      free(articleBody);	
   statusCode=[nntpServer loadArticleBody:theArticle toString:&articleBody];
   if(statusCode!=OK_BODY){
      return statusCode;
   }
	
	noArticle=FALSE;
	[self displayArticleScrollUp:YES];

   t=[theArticle time];
   if(![imageView showFaceForName:[theArticle header]->fieldBody[FROM]]){
		const char *r=[theArticle header]->fieldBody[REPLY_TO];
		if(r && *r)
			[imageView showFaceForName:r];
	}
   [[[clockView setHide:FALSE] setTime:localtime(&t)] display];
	
	return OK_BODY;
}


- displayArticleScrollUp:(BOOL)scroll
{
	Font		*textF,*sigF;
	const char *signature,*eob,*p,*thisLine;
	int			len,rows,alnum,others;
	NXSize visibleSize;
	NXRect theUpperRect;
	char *tmpArticleBody=articleBody;
	
	signatureDetection=[NXApp defaultBoolValue:DEFAULT_SIG_DETECTION];
	rewrapping=[NXApp defaultBoolValue:DEFAULT_REWRAP_ARTICLE_TEXT];
	headerMode=[NXApp defaultIntValue:DEFAULT_HEADER_MODE];

	if(noArticle)
		return self;

	// rot13 article body?
	if(rot13){
		int i,j;
		j=strlen(articleBody);
		tmpArticleBody=malloc((j+2)*sizeof(char));
		for(i=0;i<j;i++) {
        if(islower(articleBody[i]))
				tmpArticleBody[i] = (((articleBody[i]-'a')+13)%26)+'a';
        else if(isupper(articleBody[i]))
		  		tmpArticleBody[i] = (((articleBody[i]-'A')+13)%26)+'A';
		  else
		  	  tmpArticleBody[i]=articleBody[i];
      }
    }
		
	[[theText window] disableDisplay];
	[theText setAutodisplay:NO];
	[theText setText:""];

	textF=[NXApp defaultFont:DEFAULT_ARTICLE_FONT];
	
	sigF=[Font userFixedPitchFontOfSize:0 matrix:NX_FLIPPEDMATRIX];
	[theText setFont:textF];
		
   //load text in text instance
	if(headerMode==NEWSPAPER_HEADER){
		[self writeNewspaperHeader];	
	}
	else if(headerMode==FULL_HEADER){
		[theText setText:articleHeader];
	}
	else if(headerMode==SMALL_HEADER){
		[self writeSelectedHeader];      
	}
	len=[theText textLength];
	[theText setSel:len :len];
	[theText replaceSel:"\n"];

	signature=NULL;
	len=strlen(tmpArticleBody);
	if(len>4){
		eob=tmpArticleBody+len-1;
		signature=NULL;
		//find signature
		if(signatureDetection){
			rows=0;
			for(p=tmpArticleBody+len-4;p>tmpArticleBody && rows<16;p--)
				if(p[0]=='\n'){
					if(p[1]=='-' && p[2]=='-' && p[strspn(p+1,"\t -")+1]=='\n'){
						eob=p;
						signature=p+3;
						if(*signature=='-')
							signature++;
						break;
					}
					else
						rows++;
				}
		}
			
		thisLine=tmpArticleBody;
		do{
			alnum=others=1;
			for(p=thisLine;p<eob && p[0]!='\n';p++)
				if(NXIsAlNum(p[0]) || p[0]==' ')
					alnum++;
				else
					others++;
				
			if(rewrapping && p!=eob && alnum>40 && alnum/others>3 && !strchr(FORCEBREAK,p[1]))
				{
				[theText appendString:thisLine length:p-thisLine withFont:textF];
				if(p[-1]!=' ')
					[theText appendString:" " withFont:textF];
				}
			else
				[theText appendString:thisLine length:p-thisLine+1 withFont:textF];
				
			thisLine=p+1;
		}
		while(p<eob);

		if(signatureDetection && signature)
			{
			[theText appendString:"\n" withFont:sigF];
			[theText appendString:signature withFont:sigF];
			}
	}
	else
		[theText appendString:tmpArticleBody length:len withFont:textF];
	
	if(scroll){
   	[[[theText superview] superview] getContentSize:&visibleSize];
   	NXSetRect(&theUpperRect,0.0,0.0,visibleSize.width,visibleSize.height);
   	[theText scrollRectToVisible:&theUpperRect];
	}
	
   [theText setAutodisplay:YES];
   [[theText window] reenableDisplay];
   [[theText window] display];

	if(rot13)
		free(tmpArticleBody);
		
   return self;
}

- clear
{
   [theText setAutodisplay:NO];
   [theText setText:""];
   [[theText setAutodisplay:YES] display];
	
   noArticle=TRUE;
   [imageView showFaceForName:NULL];
   [[clockView setHide:TRUE] display];
	if(articleBody)
		free(articleBody);
	if(articleHeader)
		free(articleHeader);
	articleBody=NULL;
	articleHeader=NULL;
	
   return self;
}

- saveAs:sender
{
   id panel;
   int fd;
   NXStream *theStream;
   static char *dir=NULL;

   if(dir==NULL)
      dir=NXCopyStringBuffer([NXApp defaultValue:DEFAULT_SAVE_PATH]);
   if([theText textLength]==0 || noArticle)
      return self;
   panel=[SavePanel new];
   if([panel runModalForDirectory:dir file:""]!=NX_CANCELTAG){
      free(dir);
      dir=NXCopyStringBuffer([panel filename]);
   }
   else
      return nil;
   
   //Save
   fd=open([panel filename],O_WRONLY|O_CREAT|O_TRUNC,0666);
   if(fd<0){
      NXRunAlertPanel("ALEXANDRA","Can't save file: %s",NULL,NULL,NULL,strerror(errno));
      return self;
   }
   theStream=NXOpenFile(fd,NX_WRITEONLY);
	NXWrite(theStream,articleHeader,strlen(articleHeader));
	NXWrite(theStream,"\n",1);
	NXWrite(theStream,articleBody,strlen(articleBody));
   NXClose(theStream);
   close(fd);
   return self;
}

- printText:sender
{
   if([theText textLength]!=0){
		[[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
      [theText printPSCode:self];
	}
   return self;
}

- (const char *)articleBody
{
	if(noArticle)
		return NULL;
	return articleBody;
}

- (const char *)articleHeader
{
   if(noArticle)
		return NULL;
	return articleHeader;
}

- writeBody:(NXStream *)aStream
{
	if(articleBody)
		NXWrite(aStream,articleBody,strlen(articleBody));

   return self;
}

- writeQuotedText:(NXStream *)aStream
{
   int i;
   int bodyLen;
   BOOL wasNewline;
   const char *quotestring;

	if(!articleBody)
		return self;
		
   quotestring=[NXApp defaultValue:DEFAULT_QUOTING_PREFIX];
   if(!quotestring){
      [NXApp setDefault:DEFAULT_QUOTING_PREFIX to:QUOTESTRING];
      quotestring=[NXApp defaultValue:DEFAULT_QUOTING_PREFIX];
   }

	bodyLen=strlen(articleBody);
   wasNewline=TRUE;
   for(i=0;i<bodyLen;i++){
      if(wasNewline)
         NXPrintf(aStream,"%s",quotestring);
      NXPutc(aStream,articleBody[i]);
      if(articleBody[i]=='\n')
         wasNewline=TRUE;
      else
         wasNewline=FALSE;
   }   
   
   return self;
}

- writeSelectedHeader
{
   char **selectedHeaders;
   char *field_names,*f_buffer,**pattern;
   const char *default_headers;
   int count=0;
   int num_h=0;
   int i;
   BOOL start=TRUE;
	NXStream *aStream=NXOpenMemory(NULL,0,NX_READWRITE);

   default_headers=[NXApp defaultValue:DEFAULT_HEADER_FILTER];
   if(*default_headers=='\0'){
      return self;
   }

   field_names=NXCopyStringBuffer(default_headers);
   for(f_buffer=field_names;*f_buffer!='\0';f_buffer++)
      if(*f_buffer==':')
         num_h++;
   selectedHeaders=calloc(num_h,sizeof(char**));
   pattern=calloc(num_h,sizeof(char**));

   f_buffer=field_names; i=0;
   for(;*f_buffer!='\0';f_buffer++){
      if(start){
         pattern[i]=f_buffer;
         start=FALSE;
         i++;
      }
      else if(*f_buffer==':'){
         start=TRUE;
         *f_buffer='\0';
      }
   }
   
   parse_header(articleHeader,strlen(articleHeader),selectedHeaders,pattern,num_h,NO);

   i=0;   
   for(count=0;count<num_h;count++)
      if(selectedHeaders[count]!=NULL){
         i++;
         NXPrintf(aStream,"%s: %s\n",pattern[count],selectedHeaders[count]);
         free(selectedHeaders[count]);
      }
   NXPrintf(aStream,"\n");

   free(field_names);
   free(selectedHeaders);
   free(pattern);
	NXSeek(aStream,0,NX_FROMSTART);
	[theText readText:aStream];
	NXCloseMemory(aStream,NX_FREEBUFFER);

   return self;
}

- writeNewspaperHeader
{
	Font		*titleF,*authorF;
	
	NXRect visibleRect;
	int titleFsize=24;
	
	[theText getVisibleRect:&visibleRect];
	
	authorF=[Font boldSystemFontOfSize:14 matrix:NX_FLIPPEDMATRIX];

	if(!ISEMPTY(subject)){
		while(([titleF=[Font boldSystemFontOfSize:titleFsize matrix:NX_FLIPPEDMATRIX]
					getWidthOf:subject]>NX_WIDTH(&visibleRect)-10) && titleFsize>12)
			titleFsize-=2;

		[theText appendString:subject withFont:titleF];
		[theText appendString:"\n" withFont:titleF];
	}
	if(!ISEMPTY(from)){
		int nameFrom,to;
		
		rfc822_realname(from,&nameFrom,&to);
		[theText appendString:"by " withFont:authorF];
		[theText appendString:from+nameFrom length:to-nameFrom+1 withFont:authorF];
		if(!ISEMPTY(organization)){
			[theText appendString:", " withFont:authorF];
			[theText appendString:organization withFont:authorF];
		}
		[theText appendString:"\n" withFont:authorF];
	}

	return self;
}

-(id)theText
{
   return theText;
}

- setModeTo:(int)mode
{
   [NXApp setDefault:DEFAULT_HEADER_MODE toInt:mode];
   if(!noArticle)
		[self displayArticleScrollUp:NO];

   return self;
}
      
- showHeader:sender
{
   [self setModeTo:FULL_HEADER];
   return self;
}

- hideHeader:sender
{
   [self setModeTo:NO_HEADER];
   return self;
}

- smallHeader:sender
{
   [self setModeTo:SMALL_HEADER];
   return self;
}

- newspaperHeader:sender
{
	[self setModeTo:NEWSPAPER_HEADER];
	return self;
}

- (BOOL)headermodeCellEnabled:menuCell
{
   if((headerMode!=FULL_HEADER)&&([menuCell action]==@selector(showHeader:)))
      return TRUE;
   else if((headerMode!=NO_HEADER)&&([menuCell action]==@selector(hideHeader:)))
      return TRUE;
   else if((headerMode!=SMALL_HEADER)&&([menuCell action]==@selector(smallHeader:)))
      return TRUE;
	else if((headerMode!=NEWSPAPER_HEADER)&&([menuCell action]==@selector(newspaperHeader:)))
		return TRUE;
   return FALSE;
}

- rot13:sender
{
	rot13=!rot13;
	
	[self displayArticleScrollUp:NO];

	return self;
}

@end
